import java.util.Vector;


public class Kernel   {
	// Access to hardware components, including the processor 
	@SuppressWarnings("unused")
	private Memory mem;
	@SuppressWarnings("unused")
	private IntController hint;
	private ConsoleListener con;
	private Disk dis0;
	private Disk dis1;
	private CPU c0;
	private CPU c1;
	
	//Interruptions
	private final int ILEGAL_INST = 1;
	private final int TIMER_TICK = 2;
	private final int ILEGAL_ACESS = 3;
	private final int CONSOLE_INT = 15;
	private final int DISK_0_DONE = 5;
	private final int DISK_1_DONE = 6;
	private final int PROCESS_ENDED = 32;
	private final int OPEN = 34;
	private final int CLOSE = 35;
	private final int GET = 36;
	private final int PUT = 37;
	
	//Mem partitions - dummy partition doesnt count!
	private final int memPartitions = 8;
	private final int partSize = 128;
	private final int isEmpty = -1;
	private final int firstSuperPartition = 1;
	private final int secSuperPartition = 2;
	
	//Time slice for every process (in ticks)
	private final int timeout = 1;
		
	//Mem control
	private Vector<Integer> memMap;
	private int numProcesses;
	
	
	// Data used by the kernel
	public ProcessList readyList;
	//public ProcessList readyDiskList;
	
	private String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
	
	//Dummy process
	public Process dummy;
	
	// In the constructor goes initialization code
	Kernel(Memory m, ConsoleListener c, IntController i, Disk da, Disk db)
	{
		hint = i;
	    mem = m;
	    con = c;
	    dis0 = da;
	    dis1 = db;
	    c0 = new CPU(m,this,i,0,"\t\t");
	    c1 = new CPU(m,this,i,1, "\t\t\t\t\t\t");
	    
	    numProcesses = 0;
	    readyList = new ProcessList ("Ready List");
	    memMap = new Vector<Integer>();
	    
	    //Create Process and alloc special partitions
	    bootOS();
	    c0.start();
	    c1.start();
	    
	}
	
	private void bootOS()
	{
		//Inicialize memory mapping with empty partitions
		memMap.setSize(memPartitions);
		for(int i=0; i <= memPartitions; i++)
			memMap.add(i, isEmpty);
				
		// Creates process(ID,BASE,LIMIT)
				
		dummy = new Process(0,0,1);
		memMap.add(0, dummy.PID); //attach dummy to partition
		
		//Create special partitions
		memMap.add(1, firstSuperPartition);
		memMap.add(2, secSuperPartition);
		
		 //It will start on 3 because the 0,1 and 2 are IDs for partition. I know that they are not processes but they are IDs for my mapping anyway
		numProcesses = 3;
		
		//Create some processes to have fun
		Process p1 = createProcess();
		Process p2 = createProcess();
		Process p3 = createProcess();
		
		readyList.pushBack(p1);
		readyList.pushBack(p2);
		readyList.pushBack(p3);
		
		printMap();
		
	    System.out.println("Fila de processos prontos e em disco:");
	    readyList.print();
	    System.out.println();
	    readyList.print();
	    dis0.diskList.print();
	    dis1.diskList.print();
	    System.out.println();
	    
	}
	
	private void printMap()
	{
		System.out.println("Partition Mapping: ");
		for(int i=0; i <= memPartitions; i++)
			System.out.print(memMap.get(i)+"  ");
		System.out.println();
	}
	
	private Process createProcess()
	{
		Integer choosedPartition = 0;
		Process p = null;
		int base = 0, limit = 0;
		
		for(int i=0; i <= memPartitions; i++)
		{
			if(memMap.get(i) < 0)
			{
				choosedPartition = i;
				break;
			}
		}
		
		//If I found a partition
		if(choosedPartition > 0)
		{
			limit = (choosedPartition * partSize) + 1;
			base = limit - partSize;
			p = new Process(numProcesses,base,limit);
			//System.out.println("P"+p.PID+" ["+ p.getBaseRegister()+ "  "+ p.getLimitRegister() + " ]");
			numProcesses++;
			memMap.add(choosedPartition, p.PID);
			return p;
		}
		else
		{
			System.err.println("Partition table is full");
			return null;
		}
		
	}
	
  // Each time the kernel runs it have access to all hardware components
	  public synchronized Process run(int interruptNumber, Process actualPro)
	  {
		    Process newProc = null;
		    System.err.println(tabs+"Kernel called for int "+interruptNumber);
		    
	    	switch(interruptNumber)
		    {
	    		case ILEGAL_INST:
	    		  newProc = null;
				  //Free partition
	    		  System.err.println(tabs+"ILEGAL INSTRUCTION. Process " + actualPro.PID+" Killed.");
				  freePartition(actualPro);
				  //processTime = 0;
	    		break;
	    			
			    case TIMER_TICK:
			    if(actualPro != null)
			    {
			      if( (actualPro.PID > 0) && (actualPro.processTime >= timeout) && (!readyList.isEmpty()) ) //If not dummy process and timeout
			      {
			    	  actualPro.processTime = 0;
				      newProc = readyList.popFront();
				      readyList.pushBack(actualPro);
				      System.err.println(tabs+"PROCESS " + actualPro.PID+ " TIMEOUT");
			      }
			      else
			      {
			    	  actualPro.processTime++;
			    	  newProc = actualPro;
			    	  System.err.println(tabs+"No Timeout for process " +newProc.PID);
			      }
			    }
			    break;
			      
			    case ILEGAL_ACESS:
			      newProc = null;
			      System.err.println(tabs+"ILEGAL ACESS. Process " + actualPro.PID+" Killed.");
				  freePartition(actualPro);
			      //processTime = 0;
			    break;
			      
			      
			    case DISK_0_DONE:
			    	newProc = addProcessToReady(dis0, actualPro);
			    break;
			      
			    case DISK_1_DONE:
			    	newProc = addProcessToReady(dis1, actualPro);
			    break;
			    
			    case CONSOLE_INT:
			      newProc = actualPro;
			      String []line = con.getLine().split(" ");
			      System.err.println(tabs+"Operator typed: Disk " + line[0] + " and File " + line[1]);
			      Process diskProc = createProcess();
			      //Register numeration defined in spec. Similar to OPEN interrup
			  	  diskProc.reg[0] = Disk.OPERATION_LOAD;
			  	  diskProc.reg[1] = Integer.parseInt(line[0]);
			  	  diskProc.reg[2] = Integer.parseInt(line[1]);
			  	  if(diskProc.reg[1] == dis0.getID())
			  	  {
			  		  dis0.diskList.pushBack(diskProc); //List from disk.
			  		  dis0.roda();
			  	  }
			  	  else
			  	  {
			  		  dis1.diskList.pushBack(diskProc);
			  		  dis1.roda();
			  	  }		      
			    break;
			    
			    case OPEN:
			    	Disk d;
			    	//System.out.println("Reg: "+ actualPro.reg[0]+ "   " + actualPro.reg[1] + "   "+ actualPro.reg[2]);
			    	if(actualPro.reg[1] == dis0.getID())
			    		d = dis0;
			    	else
			    		d = dis1;
			    	
			    	if(d.fileExist(actualPro.reg[2]))
			    	{
		    			int fileDescriptor = actualPro.addFileDescriptor(actualPro.reg[1], actualPro.reg[2]); //Johanns definition
						if(fileDescriptor < 0) 
						{
							System.err.println(tabs+ "Impossible to OPEN file. Proc " +actualPro.PID+ " File "+ actualPro.reg[2] + " on disk "+actualPro.reg[1]);
							actualPro.reg[1] = 1; //Error
						}
						else
							actualPro.reg[1] = 0; //Sucess
						
						actualPro.reg[0] = fileDescriptor;
						newProc = actualPro; //Continue to run	
			    	}
			    	else
			    	{
			    		System.err.println(tabs+ "Impossible to OPEN file. Proc " +actualPro.PID+ " File "+ actualPro.reg[2] + " on disk "+actualPro.reg[1]);
						actualPro.reg[1] = 1; //Error
						newProc = actualPro; 
			    	}	
			    break;
			    
			    case CLOSE:
			    	actualPro.openFiles.remove(0);
			    	newProc = actualPro;
			    break;
			    
			    case GET:
			    	DiskFile file = actualPro.openFiles.remove(actualPro.reg[0]); //Put this guy on I/O
			    	actualPro.openFiles.add(0, file); //Reinsert file on first position (so we can use reg[0] to represent operation on disk
			    	actualPro.reg[0] = Disk.OPERATION_READ; //Set Operation on Register, just to give to disk
			    	//System.out.println("Reg for GET: "+ actualPro.reg[0]+ "   " + actualPro.reg[1] + "   "+ actualPro.reg[2]);
			    	//System.out.println("FILE ID = "+ file.getDiskID());
			    	if(file.getDiskID() == dis0.getID())
			    	{
			    		dis0.diskList.pushBack(actualPro);
				  		dis0.roda();
			    	}
			    	else
			    	{
			    		 dis1.diskList.pushBack(actualPro);
				  		 dis1.roda();
			    	}
			    	
			    	newProc = null; //Get another guy while I wait for disk
			    	//processTime = 0;
			    break;
			    
			    case PUT:
		    	  	DiskFile filee = actualPro.openFiles.remove(actualPro.reg[0]); //Put this guy on I/O
			    	actualPro.openFiles.add(0, filee); //Reinsert file on first position (so we can use reg[0] to represent operation on disk
			    	actualPro.reg[0] = Disk.OPERATION_WRITE; //Set Operation on Register, just to give to disk
			    	//System.out.println("Reg for PUT: "+ actualPro.reg[0]+ "   " + actualPro.reg[1]);
			    	
			    	if(filee.getDiskID() == dis0.getID())
			    	{
			    		dis0.diskList.pushBack(actualPro);
				  		dis0.roda();
			    	}
			    	else
			    	{
			    		 dis1.diskList.pushBack(actualPro);
				  		 dis1.roda();
			    	}
			    	
			    	newProc = null; //Get another guy while I wait for disk
			    	//processTime = 0;
			    break;
			    
			    case PROCESS_ENDED:
			     //Set as null just do tell cpu that the process finish
			      newProc = null;
			      freePartition(actualPro);
			      System.err.println(tabs+"Process " + actualPro.PID + " has ended");
			      //processTime = 0;
			    break;
			      
			    default:
			      System.err.println(tabs+"Unknown INT...");
			      
		}
	    	System.out.println();
		    readyList.print();
		    dis0.diskList.print();
		    dis1.diskList.print();
		    return newProc;
	    		
	}
	  
	public void freePartition(Process p)
	{
		int partition = -1;
		partition = (p.LimitRegister - 1) / partSize;
		memMap.set(partition, isEmpty);
		//printMap();
	}
	
	public Process addProcessToReady(Disk d, Process actual)
	{
		
		Process newProc = d.diskList.popFront();
	    if(newProc.reg[1] == Disk.ERRORCODE_SUCCESS)
	    {
		      readyList.pushBack(newProc);
		      System.err.println(tabs+" Disk " + d.getID() + " FINISH Proc "+newProc.PID);
		      if(actual.PID == 0)
		      {
		    	  //System.out.println(tabs+"Process "+ newProc.PID + "  Register 0 (aka disk output): "+ newProc.reg[0]);
		    	  System.err.println(tabs+"Dummy killed. CPU will now run: "+newProc.PID);
		    	  newProc.processTime = 0;
		    	  newProc = null; //I want to force CPU to get the next process on ready list
		      }
		      else
		    	  newProc = actual; //Continue the exec with the actual process
	      }
	      else
	      {
	    	  System.out.println(tabs+"Operation error of disk "+ d.getID());
	    	  newProc = actual;
	      }
	    
	      return newProc;
	}
		   
  }